/*
 * hal_saveinfo_flash
 * Copyright (c) 2022, <328674719@qq.com>
 *
 * SPDX-License-Identifier: MIT
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the 'Software'), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @eeprom  e.g: 
 *	stm32f103c8t6->last page range(0x0800FC00-0x0800FFFF) -->1K	Byte (total 64KB)
 *  stm32f103rct6->last page range(0x0803FC00-0x0803FFFF) -->2K	Byte (total 256KB)
 *	stm32f103vet6->last page range(0x0807F800-0x0807FFFF) -->2K	Byte (total 512KB)
 * 
 * Change Logs:
 * Date           Author             Notes
 * 2022-04-14     Qinglai Zhang      V0.0.2
 */




#include "hal_saveinfo_flash.h"
#include <stdio.h>
#include <string.h>


static uint16_t FlashBuffer[STM32FLASH_PAGE_SIZE >> 1];
static uint32_t FLASH_WriteNotCheck(uint32_t Address, const uint16_t *Buffer, uint32_t NumToWrite);


void FLASH_Init(void)
{
	HAL_FLASH_Unlock();
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
	HAL_FLASH_Lock();
}

/**
 * @param  Address 
 * @param  Buffer  
 * @param  Read data size in bytes
 * @return Number of bytes read successfully
 */
uint32_t FLASH_Read(uint32_t Address, void *Buffer, uint32_t Size)
{
	uint32_t nread = Size;
	uint8_t* d = (uint8_t *)Buffer;
	const uint8_t* s = (const uint8_t *)Address;

	if (!Buffer || Address < STM32FLASH_BASE || ((Address + Size) >= STM32FLASH_END))
		return 0;

	while (nread >= sizeof(uint32_t) && (((uint32_t)s) <= (STM32FLASH_END - 4)))
	{
		*(uint32_t *)d = *(uint32_t *)s;
		d += sizeof(uint32_t);
		s += sizeof(uint32_t);
		nread -= sizeof(uint32_t);
	}

	while ((nread != 0) && (((uint32_t)s) < STM32FLASH_END))
	{
		*d++ = *s++;
		nread--;
	}
	return (Size - nread);
}

/**
 * @param  Address    Write start address, 2-byte alignment required
 * @param  Buffer     The data to be written requires 2-byte alignment
 * @param  NumToWrite The amount of data to be written, in half a word. 
 * @return The amount of data actually written, in bytes           
 */
uint32_t FLASH_Write(uint32_t Address, const uint16_t *Buffer, uint16_t NumToWrite)
{
	uint32_t i = 0;
	uint32_t pagepos = 0;        
	uint32_t pageoff = 0;         
	uint32_t pagefre = 0;         
	uint32_t offset = 0;          
	uint32_t nwrite = NumToWrite; 

	/* Illegal address */
	if (Address < STM32FLASH_BASE || Address > (STM32FLASH_END - 2) || NumToWrite == 0 || Buffer == NULL)
		return 0;
	HAL_FLASH_Unlock();
	offset = Address - STM32FLASH_BASE;
	pagepos = offset / STM32FLASH_PAGE_SIZE;
	pageoff = ((offset % STM32FLASH_PAGE_SIZE) >> 1);
	pagefre = ((STM32FLASH_PAGE_SIZE >> 1) - pageoff);
	if (nwrite <= pagefre)
		pagefre = nwrite;
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
	while (nwrite != 0)
	{
		if (pagepos >= STM32FLASH_PAGE_NUM)
			break;
		FLASH_Read(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE);
		for (i = 0; i < pagefre; i++)
		{
			if (*(FlashBuffer + pageoff + i) != 0xFFFF) /* FLASH After erasing, the default is0xFF */
				break;
		}
		if (i < pagefre)
		{
			uint32_t count = 0;
			uint32_t index = 0;
			uint32_t PageError = 0;
			FLASH_EraseInitTypeDef pEraseInit;
			pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
			pEraseInit.PageAddress = STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE;
			pEraseInit.Banks = FLASH_BANK_1;
			pEraseInit.NbPages = 1;
			if (HAL_FLASHEx_Erase(&pEraseInit, &PageError) != HAL_OK)
				break;

			for (index = 0; index < pagefre; index++)
			{
				*(FlashBuffer + pageoff + index) = *(Buffer + index);
			}

			count = FLASH_WriteNotCheck(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE >> 1);
			if (count != (STM32FLASH_PAGE_SIZE >> 1))
			{
				nwrite -= count;
				break;
			}
		}
		else
		{
			uint32_t count = FLASH_WriteNotCheck(Address, Buffer, pagefre);
			if (count != pagefre)
			{
				nwrite -= count;
				break;
			}
		}

		Buffer += pagefre;      
		Address += (pagefre << 1); 
		nwrite -= pagefre;        

		pagepos++;           
		pageoff = 0;   

		pagefre = nwrite >= (STM32FLASH_PAGE_SIZE >> 1) ? (STM32FLASH_PAGE_SIZE >> 1) : nwrite;
	}

	HAL_FLASH_Lock();

	return ((NumToWrite - nwrite) << 1);
}

/**
 * @param  Address    Write start address, 2-byte alignment required
 * @param  Buffer     The data to be written requires 2-byte alignment
 * @param  NumToWrite The amount of data to be written, in half a word. 
 * @return The amount of data actually written, in bytes           
 */
static uint32_t FLASH_WriteNotCheck(uint32_t Address, const uint16_t *Buffer, uint32_t NumToWrite)
{
	uint32_t nwrite = NumToWrite;
	uint32_t addrmax = STM32FLASH_END - 2;

	while (nwrite)
	{
		if (Address > addrmax)
			break;

		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, Address, *Buffer);
		if ((*(volatile uint16_t*) Address) != *Buffer)
			break;

		nwrite--;
		Buffer++;
		Address += 2;
	}
	return (NumToWrite - nwrite);
}


